
import json
import os

js_type_file='codesecurity/feature/node-types.json'
node_types_instance_js=None

def JSNodeTypes():
    return NodeTypes_Js.default()

class NodeTypeBuilder:
    @staticmethod
    def build_node_type_js(json_obj):
        def build_subtypes(node_type_obj,subtype_json):
            if subtype_json is None or len(subtype_json)==0:
                return
            
            for subtype in subtype_json:
                node_type_obj.subtypes.append(subtype['type'])
                
        
        def build_fields(node_type_obj,field_json):
            if field_json is None or len(field_json)==0:
                return
            
            for field_name in field_json:
                field=field_json[field_name]
                if field_name not in node_type_obj.fields:
                    node_type_obj.fields[field_name]=[]
                    
                node_type_obj.fields[field_name]+=[e['type'] for e in field['types']]
        
        def build_children(node_type_obj,children_json):
            if children_json is None or len(children_json)==0:
                return
            
            node_type_obj.children+=[e['type'] for e in children_json['types']]
            
        
        obj=NodeType_Js(json_obj['type'])
        
        build_subtypes(obj,json_obj.get('subtypes',[]))
        build_fields(obj,json_obj.get('fields',[]))
        build_children(obj,json_obj.get('children',[]))
        
        return obj
    
    
class NodeType_Js:
    def __init__(self,node_type) -> None:
        self.node_type=node_type
        self.subtypes=[]
        self.fields={}
        self.children=[]
    
    def __str__(self) -> str:
        return self.node_type
    
    def has_subtypes(self):
        return len(self.subtypes)>0

    def has_children(self):
        return len(self.children)>0
    
    def has_fields(self):
        return len(self.fields)>0
    
    
    
    @property
    def field_names(self):
        return list(self.fields.keys())
    
# List JavaScript Abstract Syntax Tree Node Type
class NodeTypes_Js:
    def __init__(self,node_types) -> None:
        self.name2types={}
        for node_type in node_types:
            self.name2types[node_type.node_type]=node_type

        self.name2id={}
        for name in self.name2types:
            self.name2id[name]=len(self.name2id)

    def list_node_subtypes(self,node_type):
        if node_type not in self.name2types:
            return []
        
        if not self.name2types[node_type].has_subtypes():
            return [self.name2types[node_type]]
        
        ret=[]
        for subtype in self.name2types[node_type].subtypes:
            ret.extend(self.list_node_subtypes(subtype))
        return ret

    def list_names(self):
        return list(self.name2types.keys())
    
    def declaration_types(self):
        node_type='declaration'
        
        return self.list_node_subtypes(node_type)
    
    def identifier_types(self):
        node_type='identifier'
        
        return self.list_node_subtypes(node_type)

    def statement_types(self):
        node_type='statement'
        
        return self.list_node_subtypes(node_type)
    
    def expression_types(self):
        node_type='expression'
        
        return self.list_node_subtypes(node_type)
    
    def primary_types(self):
        node_type='primary'
        
        return self.list_node_subtypes(node_type)
    
    def pattern_types(self):
        node_type='pattern'
        
        return self.list_node_subtypes(node_type)
    
    # def single_gram_types(self):
    #     ret=[]
        
    #     return ret
    
    def field_with_type(self,field_name):
        ret=[]
        for node_type in self.list_names():
            fields=[]
            for e in self.name2types[node_type].fields:
                node_fields=self.name2types[node_type].fields[e]
                for node_field in node_fields:
                    fields.extend(self.list_node_subtypes(node_field))
            fields=[e.node_type for e in fields]
            if field_name in fields:
                ret.append(node_type)
        return ret
    
    def children_with_type(self,child_type):
        ret=[]
        for node_type in self.list_names():
            children=[]
            for e in self.name2types[node_type].children:
                children.extend(self.list_node_subtypes(e))
            children=[e.node_type for e in children]
            if child_type in children:
                ret.append(node_type)
        return ret
    
    
    
    @staticmethod
    def default():
        global node_types_instance_js
        
        detect_paths=['codesecurity/feature/node-types.json','../codesecurity/feature/node-types.json','node-types.json']
        
        choose_path=None
        for e in detect_paths:
            if os.path.exists(e):
                choose_path=e
                break
        
        assert choose_path is not None
        
        if node_types_instance_js is None:
            with open(choose_path,'r') as f:
                js_types=json.load(f)
            
            node_types=[]
            for e in js_types:
                node_types.append(NodeTypeBuilder.build_node_type_js(e))
            
            node_types_instance_js=NodeTypes_Js(node_types)

        return node_types_instance_js

if __name__=="__main__":
    with open(js_type_file,'r') as f:
        js_types=json.load(f)
    
    node_types=[]
    for e in js_types:
        node_types.append(NodeTypeBuilder.build_node_type_js(e))
    
    node_types=NodeTypes_Js(node_types)
    
    print(len(node_types.list_names()))
    
    # for name in node_types.list_names():
    #     print(name)
    #     print([str(e) for e in node_types.list_node_subtypes(name)])
    #     print([str(e) for e in node_types.name2types[name].fields])
    #     print([str(e) for e in node_types.name2types[name].children])
    #     print()
    
    